[译文]SLUB 内存分配器
原文链接:http://lwn.net/Articles/229984/
原文作者:corbet
原文发表时间:April 11, 2007
译者:王旭
翻译时间:2008年1月14日
译者按:不知道读者朋友们有没有误入过 /sys/slab 目录,进过这个吓人的目录之后,你可能就很想知道它到底是怎么回事,这和 slab 内存分配器有关,当然,更和 SLUB 内存分配器相关了,/sys/slab 和 slub 一同在 2.6.22 内核被引入,用于提升 cache 内存分配在多处理器系统中的效率。嗯,接下来还是看看这篇不是很久远的考古译文。
***
补充一下,目前SLUB也不是一帆风顺的,和 /sys/slab 有关的东东也有很多问题,http://lwn.net/Articles/263329/ 这篇文章和之后的评论指出了这些问题,让我们拭目以待吧,好了,现在可以继续看这篇考古译文了。
***
slab内存分配器多年以来一直位于内核的内存管理部分的核心地带,它(在底层页分配器之上)管理着特定大小的对象的缓存,允许快速而且省空间的内存分配。内核黑客们一般不愿意去动slab的带脉,因为它实在是非常复杂,而且在大多数情况下,它的工作完成的相当不错。
Christoph Lameter就是那些感到 slab 工作的不是那么好的少数人之一。长时间以来,他提出了一个长长的关于 slab 的问题列表。slab 分配器维护了大量的对象队列,这些队列可以让内存分配更快,却也增加了复杂度。此外,这些存储开销会随着系统规模增大而增大:
每个节点、每个CPU都有 slab 对象队列。alien cache 队列甚至有一个为每个节点的每个cpu都保存一个队列的队列数组。对于大规模的系统,队列数量和对象数量甚至会指数增长。在我们的有一千个节点/处理器的 系统中,大约有几G字节的内存被用于存放那些队列,这还不包括哪些在这些队列上的对象。恐怕有一天所有的内存会都消耗在这些队列上头。
而且,每个 slab (一组一个或多个连续的用于分配对象的页面) 都要在起始处包含大量的元数据 ,这使得对象的对其进一步变得困难了。而用于在内存紧张的时候情理 cache 的代码又进一步增加了复杂度……
Christoph 对此的处理是提出了一个SLUB allocator,用于替代 slab 代码。通过取消了大量的队列和相关开销、简化 slab 的结构,SLUB 承诺提供更好的性能和更好的系统可伸缩性,并且可以同时保持现有的 slab 分配器接口。
在 SLUB 分配器中,一个 slab 就是一组一个或多个页面,封装了固定大小的 对象。slab 内部没有元数据,只是空闲对象组织在简单的链表之中。当有一个分配请求的时候,第一个空闲对象就此被定位、从列表中删除并返还给调用者。
在缺少每个slab的元数据的情况下,你可能会非常好奇第一个空闲的对象是如何被发现的。答案就在于 SLUB 分配器将这些信息存储在了系统内存映像之中 — 与这个 slab 所在的页面相关的页结构。让 struct page 变大同样是让人不能认同的,所以SLUB 分配器通过增加了另一个 union 来让这个复杂的结构更复杂了一些,从而解决了这个问题。最终结果是 struct page 增加了三个只在作为 slab 组成部分时才有效的字段:
void *freelist; short unsigned int inuse; short unsigned int offset;
在用于 slab 时,freelist 指向 slab 中的第一个空闲对象,inuse 是 slab 已经分配出去的对象数量,而通过 offset ,分配器可以知道那里可以找到指向链表中下一个空闲对象的指针。SLUB 分配器可以使用 RCU 来释放对象,但是,如果想这么做,它必须能够将“下一个对象”的指针放在对象之外;offset 指针正是 SLUB 用来跟踪指针放置位置的手段。
当 slab 被分配器创建的时候,没有对象从中被分配过。一旦一个对象被分配了,它也就成为了一个存储在 kmem_cache 结构中的一个链表中的“部分的” slab。作为一个以可伸缩性为目标的补丁,系统中的每个 NUMA 节点会有一个“部分的”链表。分配器尽力保证分配节点本地的内存,但是会在部分 slab 填满系统之前到达其他节点的。
另外,还有一个每CPU的激活 slab 的数组,用于防止包括 NUMA 节点内部的各种 cache line bouncing。 同时还有一个特别的线程(通过一个工作队列)运行,监视每 CPU slab 的使用情况,如果一个CPU的 slab 没有被用上,它还会被放回到部分列表中,以便被其他进程使用。
如果一个 slab 中的所有对象都被分配出去了,分配器就可以完全不用考虑这个 slab 了。一旦一个满 slab 中的一个对象被释放了,分配器就可以可以通过系统内存映像来重定位这个 slab,并将它放回到相应的部分链表之中去。如果一个给定的 slab 中的所有对象(通过 inuse 计数器标记)都被释放了,那么整个 slab 都将被放回到页分配器中,以便重用。
SLUB 内存分配器的一个有趣的特性是它可以合并多个有相似对象尺寸和参数的 slab。其结果是系统中可以有更少的 slab 缓存(据说可以减少 50%),更好的本地化 slab 分配和更少的 slab 内存碎片。该补丁声明:
这个合并可以暴露出内核中迄今为止尚不为人所知的 bug,因为坏掉的对象现在可能难于放置,并会影响到临近的对象。请打开 sanity check 来发现这些问题。
让 bug 暴露出来总的来说是件好事,不过,过多使用 SLUB 分配器在那些新的 bug 没有被找出来之前会引起一些奇异行为。
过于广泛的使用是完全可能的:SLUB 分配器现在已经在 -mm 树之中了,并且可能会被加入到 2.6.22 主线内核当中。简化的代码非常诱人,据说有 5-10% 的性能提升。如果被加入到主线内核当中,SLUB 可以和当前的 slab 分配器 (以及面向小系统的 SLOB)共存相当常一段时间。从长远来看,当前的 slab 代码可能就要到了生命的终点了。